/*
 * Copyright (C) 2009,2010,2011  Paulo Cesar Pereira de Andrade.
 *
 * This is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * Authors:
 *	Paulo Cesar Pereira de Andrade
 */

#include "exl.h"
#include <errno.h>

#define THREAD_STACK_SIZE	1024 * 256

/*
 * Prototypes
 */
static void
builtin_thread(eint32_t ac);

static void
builtin_join(eint32_t ac);

static void
builtin_mutex(eint32_t ac);

static void
builtin_lock(eint32_t ac);

static void
builtin_unlock(eint32_t ac);

/*
 * Initialization
 */
__thread ethread_t	*thread_self;


#if !JITVM
static char		 ethread_exit_code[] = { eop_exit };
#endif

/*
 * Implementation
 */
void
init_thread(void)
{
    esymbol_t	*symbol;

    symbol = enew_symbol(ht_language, eget_string((euint8_t *)"thread", 6));
    edefine_builtin(enew_bound_symbol(symbol), builtin_thread, 1, true);

    symbol = enew_symbol(ht_language, eget_string((euint8_t *)"join", 4));
    edefine_builtin(enew_bound_symbol(symbol), builtin_join, 1, false);

    symbol = enew_symbol(ht_language, eget_string((euint8_t *)"mutex", 5));
    edefine_builtin(enew_bound_symbol(symbol), builtin_mutex, 0, false);

    symbol = enew_symbol(ht_language, eget_string((euint8_t *)"lock", 4));
    edefine_builtin(enew_bound_symbol(symbol), builtin_lock, 1, false);

    symbol = enew_symbol(ht_language, eget_string((euint8_t *)"unlock", 6));
    edefine_builtin(enew_bound_symbol(symbol), builtin_unlock, 1, false);
}

static void
builtin_thread(eint32_t ac)
{
    evalue_t		*av;
    ethread_t		*thread;
    eint32_t		 length;
    pthread_attr_t	 attr;

    --ac;
    length = ac;
    av = thread_self->esp - 1;
    /* check if thread function is valid */
    if (av->t != t_lambda || (av->v.y->bits & L_builtin))
	ethrow(except_invalid_argument);
    if (length < av->v.y->args)
	ethrow(except_invalid_argument);
    else if (!(av->v.y->bits & L_varargs) && length > av->v.y->args)
	ethrow(except_invalid_argument);

    /* allocate new thread with default stack length */
    if (length + 2 < 8192)	length = 8192;
    else {
	length = length + 2 + 8192;
	length = length - (length & 8191);
    }

    enew_thread(&thread_self->obj, length);
    thread = (ethread_t *)thread_self->obj;

    thread->vcount = length - av->v.y->args;

    /* where to start executing */
    thread->eip = av->v.y->code.lambda;

    thread->esp += ac + 2;
    /* copy arguments to new stack */
    memcpy(thread->esp - (ac + 2), av - ac, ac * sizeof(evalue_t));
    /* build return code */
    thread->esp[-2].v.o = ethread_exit_code;
    thread->esp[-1].v.o = thread->ebp;
    thread->ebp = thread->esp - 1;

    pthread_attr_init(&attr);
#if 0
    if (pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED))
	eerror("pthread_attr_setdetachstate: %s", strerror(errno));
#endif
    if (pthread_attr_setstacksize(&attr, THREAD_STACK_SIZE))
	eerror("pthread_attr_setstacksize: %s", strerror(errno));
    pthread_create(&thread->pthread, &attr,
		   (void*(*)(void*))&evm, thread);
    pthread_attr_destroy(&attr);

    /* return thread handler */
    thread_self->ret.v.o = thread;
    thread_self->ret.t = t_thread;
}

static void
builtin_join(eint32_t ac)
{
    ethread_t	*thread;

    if (thread_self->esp[-1].t != t_thread)
	ethrow(except_invalid_argument);
    thread = thread_self->esp[-1].v.o;

    if (!pthread_equal(thread_self->pthread, thread->pthread)) {
	if ((errno = pthread_join(thread->pthread, null)))
	    eerror("pthread_join: %s", strerror(errno));
    }
}

static void
builtin_mutex(eint32_t ac)
{
    pthread_mutex_t	*mutex;

    enew_object(&thread_self->obj, t_mutex, sizeof(pthread_mutex_t));
    mutex = thread_self->obj;
    emutex_init(mutex);

    thread_self->ret.v.o = mutex;
    thread_self->ret.t = t_mutex;
}

static void
builtin_lock(eint32_t ac)
{
    pthread_mutex_t	*mutex;

    if (thread_self->esp[-1].t != t_mutex)
	ethrow(except_invalid_argument);
    mutex = thread_self->esp[-1].v.o;

    emutex_lock(mutex);
}

static void
builtin_unlock(eint32_t ac)
{
    pthread_mutex_t	*mutex;

    if (thread_self->esp[-1].t != t_mutex)
	ethrow(except_invalid_argument);
    mutex = thread_self->esp[-1].v.o;

    emutex_unlock(mutex);
}
